<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>


<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />

<meta name="theme-color" content="#f8f5ec" />
<meta name="msapplication-navbutton-color" content="#f8f5ec">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="#f8f5ec">



  <meta name="description" content="微信公众号开发Django-网页授权"/>




  <meta name="keywords" content="Django, 微信公众号, 八一" />



  <meta name="baidu-site-verification" content="HhUstaSjr0" />



  <meta name="google-site-verification" content="UA-102975942-1" />






  <link rel="alternate" href="/atom.xml" title="八一">




  <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico?v=2.6.0" />



<link rel="canonical" href="https://bay1.top/2018/06/21/微信公众号开发Django-网页授权/"/>


<link rel="stylesheet" type="text/css" href="/css/style.css?v=2.6.0" />
<link rel="stylesheet" type="text/css" href="/css/prettify.css" media="screen" />
<link rel="stylesheet" type="text/css" href="/css/sons-of-obsidian.css" media="screen" />



  <link rel="stylesheet" type="text/css" href="/lib/fancybox/jquery.fancybox.css" />




  
  <script id="baidu_analytics">
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?9a885cc9fb6cd7bcef579deb8efe8a70";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>



  <script id="google_analytics">
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
        (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
        m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
        })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

        ga('create', 'UA-102975942-1', 'auto');
        ga('send', 'pageview');
  </script>










    <title> 微信公众号开发Django-网页授权 - 八一 </title>
  </head>

  <body><div id="mobile-navbar" class="mobile-navbar">
  <div class="mobile-header-logo">
    <a href="/." class="logo">八一</a>
  </div>
  <div class="mobile-navbar-icon">
    <span></span>
    <span></span>
    <span></span>
  </div>
</div>

<nav id="mobile-menu" class="mobile-menu slideout-menu">
  <ul class="mobile-menu-list">
    
      <a href="/archives">
        <li class="mobile-menu-item">
          
          
            文章
          
        </li>
      </a>
    
      <a href="/tags">
        <li class="mobile-menu-item">
          
          
            标签
          
        </li>
      </a>
    
      <a href="/about">
        <li class="mobile-menu-item">
          
          
            关于/友链
          
        </li>
      </a>
    
      <a href="/search">
        <li class="mobile-menu-item">
          
          
            站内搜索
          
        </li>
      </a>
    
  </ul>
</nav>

    <div class="container" id="mobile-panel">
      <header id="header" class="header"><div class="logo-wrapper">
  <a href="/." class="logo">八一</a>
</div>

<nav class="site-navbar">
  
    <ul id="menu" class="menu">
      
        <li class="menu-item">
          <a class="menu-item-link" href="/archives">
            
            
              文章
            
          </a>
        </li>
      
        <li class="menu-item">
          <a class="menu-item-link" href="/tags">
            
            
              标签
            
          </a>
        </li>
      
        <li class="menu-item">
          <a class="menu-item-link" href="/about">
            
            
              关于/友链
            
          </a>
        </li>
      
        <li class="menu-item">
          <a class="menu-item-link" href="/search">
            
            
              站内搜索
            
          </a>
        </li>
      
    </ul>
  
</nav>

      </header>

      <main id="main" class="main">
        <div class="content-wrapper">
          <div id="content" class="content">
            
  
  <article class="post">
    <header class="post-header">
      <h1 class="post-title">
        
          微信公众号开发Django-网页授权
        
      </h1>

      <div class="post-meta">
        <span class="post-time">
          2018-06-21
        </span>
        
        
        
      </div>
    </header>

    
    
  <div class="post-toc" id="post-toc">
    <h2 class="post-toc-title">文章目录</h2>
    <div class="post-toc-content">
      <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#code"><span class="toc-text">code</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#access-token"><span class="toc-text">access_token</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#refresh-token"><span class="toc-text">refresh_token</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#snsapi-userinfo"><span class="toc-text">snsapi_userinfo</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#关于缓存"><span class="toc-text">关于缓存</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#os-environ-get-‘’"><span class="toc-text">os.environ.get(‘’)</span></a></li></ol>
    </div>
  </div>


    <div class="post-content">
      
        <blockquote>
<p>对于基础的微信公众号开发，网页授权,JSSDK,图片处理应该是最重要的三部分了<br>根本上也是按照文档开发，技术含量并不高。</p>
</blockquote>
<a id="more"></a>
<p>(选Django=很多权限控制模块已经做好了,比较省力)</p>
<blockquote>
<p>在开始之前极力推荐内网穿透的各种工具，可以实时调试公众号<br>推荐自己搭建，可以自定义域名，传输也放心<br><a href="https://github.com/inconshreveable/ngrok" target="_blank" rel="noopener">https://github.com/inconshreveable/ngrok</a><br><a href="https://github.com/fatedier/frp" target="_blank" rel="noopener">https://github.com/fatedier/frp</a></p>
</blockquote>
<p>不想手动实现全部的可以参考 <a href="https://github.com/offu/WeRoBot" target="_blank" rel="noopener">WeRoBot</a><br>一开始就打算用这个，省力一下，不过它也有部分接口没有实现<br>改着改着，导致到最后。。。。不经意地自己实现了’小封装’<br>不过只用一些基础接口还是直接用框架比较方便</p>
<p><img src="https://s1.ax1x.com/2018/06/21/PS5Rm9.png" alt=""></p>
<p>网页授权,直接摘抄官方文档:</p>
<ul>
<li><p>第一步：用户同意授权，获取code</p>
</li>
<li><p>第二步：通过code换取网页授权access_token</p>
</li>
<li><p>第三步：刷新access_token（如果需要）</p>
</li>
<li><p>第四步：拉取用户信息(需scope为 snsapi_userinfo)</p>
</li>
<li><p>附：检验授权凭证（access_token）是否有效</p>
</li>
</ul>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># xgc.wechat.py部分</span></span><br><span class="line"></span><br><span class="line">token=os.environ.get(<span class="string">'TOKEN'</span>) <span class="comment"># 文末会提到</span></span><br><span class="line">app_id=os.environ.get(<span class="string">'APP_ID'</span>)</span><br><span class="line">app_secret=os.environ.get(<span class="string">'APP_SECRET'</span>)</span><br><span class="line">redirect_url = os.environ.get(<span class="string">'REDIRECT_URL'</span>)</span><br><span class="line">state = os.environ.get(<span class="string">'STATE'</span>)</span><br><span class="line"><span class="comment">#网页授权部分</span></span><br><span class="line">web_get_code = <span class="string">'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&amp;redirect_uri=%s&amp;response_type=code&amp;state=%s&amp;scope='</span>%(app_id,redirect_url,state)</span><br><span class="line">web_get_fan_info = <span class="string">'https://api.weixin.qq.com/sns/userinfo?access_token='</span></span><br><span class="line">web_check_access_token = <span class="string">'https://api.weixin.qq.com/sns/auth?access_token='</span></span><br><span class="line">web_get_access_token = <span class="string">'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&amp;secret=%s&amp;grant_type=authorization_code&amp;code='</span>%(app_id,app_secret)</span><br><span class="line"></span><br><span class="line"><span class="comment"># views.py部分</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">web_authorization</span><span class="params">(View)</span>:</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">get</span><span class="params">(self, request)</span>:</span></span><br><span class="line">        <span class="keyword">from</span> xgc.wechat <span class="keyword">import</span> web_get_code, web_get_fan_info,web_get_access_token,web_check_access_token</span><br><span class="line">        code = request.GET.get(<span class="string">"code"</span>, <span class="string">""</span>)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> code:</span><br><span class="line">            <span class="keyword">return</span> HttpResponse(<span class="string">"非法访问"</span>)</span><br><span class="line">        requests_web_get_access_token_result = requests.get(web_get_access_token+code).json()</span><br><span class="line">        <span class="keyword">if</span> <span class="string">'errmsg'</span> <span class="keyword">in</span> requests_web_get_access_token_result.keys():</span><br><span class="line">            <span class="keyword">return</span> HttpResponseRedirect(web_get_code+<span class="string">'snsapi_base#wechat_redirect'</span>)</span><br><span class="line">        <span class="keyword">if</span> cache.has_key(<span class="string">'web_access_token'</span>):</span><br><span class="line">            web_access_token = cache.get(<span class="string">'web_access_token'</span>)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            web_access_token = requests_web_get_access_token_result[<span class="string">'access_token'</span>]</span><br><span class="line">            cache.set(<span class="string">'web_access_token'</span>,web_access_token,<span class="number">110</span>*<span class="number">60</span>) <span class="comment">#下面会有说明</span></span><br><span class="line">        fan_openid = requests_web_get_access_token_result[<span class="string">'openid'</span>]</span><br><span class="line">        <span class="comment"># 这部分检验对于缓存来说是多余的，但是你如果担心缓存出问题，可以再次检验一波</span></span><br><span class="line">        <span class="comment"># check_access_token = requests.get(web_check_access_token+'%s&amp;openid=%s'%(web_access_token,fan_openid)).json()</span></span><br><span class="line">        <span class="comment"># if check_access_token['errcode']!=0:</span></span><br><span class="line">        <span class="comment">#     return HttpResponseRedirect(web_get_code+'snsapi_base#wechat_redirect')</span></span><br><span class="line">        scope = requests_web_get_access_token_result[<span class="string">"scope"</span>]</span><br><span class="line">        <span class="keyword">if</span> scope == <span class="string">"snsapi_userinfo"</span>:</span><br><span class="line">            fan_info = requests.get(web_get_fan_info+<span class="string">'%s&amp;openid=%s&amp;lang=zh_CN'</span> % (web_access_token, fan_openid))</span><br><span class="line">            fan_info.encoding = <span class="string">'utf-8'</span></span><br><span class="line">            fan_info = fan_info.json()</span><br><span class="line">            FansProfile.create_fan(fan_info)</span><br><span class="line">        check_openid = FansProfile.objects.filter(openid=fan_openid).first()</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> check_openid:</span><br><span class="line">            <span class="keyword">return</span> render(request, <span class="string">'register.html'</span>, &#123;<span class="string">'url'</span>: web_get_code+<span class="string">'snsapi_userinfo#wechat_redirect'</span>,<span class="string">'header'</span>:<span class="string">'认证提示'</span>,<span class="string">'text'</span>:<span class="string">'你还未进行信息认证,请进行第一次认证'</span>&#125;)</span><br><span class="line">        response = HttpResponseRedirect(<span class="string">'index'</span>)</span><br><span class="line">        response.set_cookie(<span class="string">'fanid'</span>,check_openid.id,<span class="number">3600</span>)</span><br><span class="line">        <span class="keyword">return</span> response</span><br></pre></td></tr></table></figure>
<h2 id="code"><a href="#code" class="headerlink" title="code"></a>code</h2><blockquote>
<p>这里我直接在公众号菜单部分设置了引导url<br>这里设置成base授权，是因为在代码里添加了认证部分<br>用户第一次认证后我们就能保存用户基本信息了，下一次就没必要在进行userinfo授权了<br>而且静默授权用户体验也更好点</p>
</blockquote>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="string">"type"</span>: <span class="string">"view"</span>,</span><br><span class="line">    <span class="string">"name"</span>: <span class="string">"申请活动"</span>,</span><br><span class="line">    <span class="string">"url"</span>: wechat_get_code+<span class="string">'snsapi_base#wechat_redirect'</span>,</span><br><span class="line">    <span class="string">"key"</span>: <span class="string">"test_4"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="access-token"><a href="#access-token" class="headerlink" title="access_token"></a>access_token</h2><p>如文档所说，在获取code之后直接请求换取我们需要的access_token</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> cache.has_key(<span class="string">'web_access_token'</span>):</span><br><span class="line">    web_access_token = cache.get(<span class="string">'web_access_token'</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    web_access_token = requests_web_get_access_token_result[<span class="string">'access_token'</span>]</span><br><span class="line">    cache.set(<span class="string">'web_access_token'</span>,web_access_token,<span class="number">110</span>*<span class="number">60</span>)</span><br></pre></td></tr></table></figure>
<p>代码中采用了缓存策略，微信中很多token是有请求次数限制的<br>而且都有一段失效期，缓存token是一个明智的选择<br>具体的缓存下面会提到</p>
<h2 id="refresh-token"><a href="#refresh-token" class="headerlink" title="refresh_token"></a>refresh_token</h2><p>换取access_token的同时微信会返回一个refresh_token<br>用于对access_token的刷新，不过我的代码中没有采用<br>而是直接采取了重新授权</p>
<h2 id="snsapi-userinfo"><a href="#snsapi-userinfo" class="headerlink" title="snsapi_userinfo"></a>snsapi_userinfo</h2><blockquote>
<p>在第一次授权的时候我们检测数据库中是否有此用户的认证记录<br>如果没有则跳转引导用户进行userinfo授权</p>
</blockquote>
<p>其中我们需要注意的是这里微信服务器返回的数据没有指明数据编码类型导致直接读取会出现乱码<br>我们可以指定response编码进行转码<br>具体解释推荐这个帖子<a href="https://www.v2ex.com/t/172276" target="_blank" rel="noopener">https://www.v2ex.com/t/172276</a></p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> scope == <span class="string">"snsapi_userinfo"</span>:</span><br><span class="line">    fan_info = requests.get(web_get_fan_info+<span class="string">'%s&amp;openid=%s&amp;lang=zh_CN'</span> % (web_access_token, fan_openid))</span><br><span class="line">    fan_info.encoding = <span class="string">'utf-8'</span></span><br><span class="line">    fan_info = fan_info.json()</span><br><span class="line">    FansProfile.create_fan(fan_info)</span><br></pre></td></tr></table></figure>
<p>PS:set_cookie是因为我没有把粉丝的模型继承Django的user模型<br>不能使用自身的auth模块，后期提交表单，上传图片又需要知道是谁上传的<br>就采用记录用户id到cookie的方式</p>
<h2 id="关于缓存"><a href="#关于缓存" class="headerlink" title="关于缓存"></a>关于缓存</h2><p>此处采用Redis的方式<br>首先Django的settings.py引入缓存配置</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">CACHES = &#123;</span><br><span class="line">    <span class="string">'default'</span>: &#123;</span><br><span class="line">        <span class="string">'BACKEND'</span>: <span class="string">'django_redis.cache.RedisCache'</span>, <span class="comment">#本地文件缓存</span></span><br><span class="line">        <span class="string">'LOCATION'</span>: <span class="string">'127.0.0.1:6379'</span>,</span><br><span class="line">        <span class="string">'TIMEOUT'</span>: <span class="number">600</span>,</span><br><span class="line">        <span class="string">'OPTIONS'</span>: &#123;</span><br><span class="line">            <span class="string">"CLIENT_CLASS"</span>: <span class="string">"django_redis.client.DefaultClient"</span>,</span><br><span class="line">            <span class="string">"IGNORE_EXCEPTIONS"</span>: <span class="keyword">True</span>,</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>然后我们就可以通过引入from django.core.cache import cache模块<br>进行一些基本的操作,比如下面</p>
</blockquote>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 设置web_access_token键值</span></span><br><span class="line"><span class="comment"># 有效期110*60s，即110min</span></span><br><span class="line">cache.set(<span class="string">'web_access_token'</span>,web_access_token,<span class="number">110</span>*<span class="number">60</span>)</span><br></pre></td></tr></table></figure>
<p><a href="https://docs.djangoproject.com/zh-hans/2.0/topics/cache/" target="_blank" rel="noopener">官方文档</a></p>
<h2 id="os-environ-get-‘’"><a href="#os-environ-get-‘’" class="headerlink" title="os.environ.get(‘’)"></a>os.environ.get(‘’)</h2><blockquote>
<p>这里获取根目录.env文件的各种配置<br>可以针对部分秘密token,app_id进行单独配置</p>
</blockquote>
<p>由此建立虚拟环境推荐工具<a href="https://github.com/pypa/pipenv/" target="_blank" rel="noopener">Pipenv</a><br>而不是常用的virtualenv等~<br>默认支持.env的导入</p>

      
    </div>

    
      
      



      
      
    

    
      <footer class="post-footer">
        
          <div class="post-tags">
            
              <a href="/tags/Django/">Django</a>
            
              <a href="/tags/微信公众号/">微信公众号</a>
            
          </div>
        
        
        
  <nav class="post-nav">
    
      <a class="prev" href="/2018/06/21/微信公众号开发Django-JSSDK授权/">
        <i class="iconfont icon-left"></i>
        <span class="prev-text nav-default">微信公众号开发Django-JSSDK授权</span>
        <span class="prev-text nav-mobile">上一篇</span>
      </a>
    
    
      <a class="next" href="/2018/06/03/nodebb搭建-维护-discuz-数据迁移/">
        <span class="next-text nav-default">nodebb搭建,维护,discuz!数据迁移</span>
        <span class="prev-text nav-mobile">下一篇</span>
        <i class="iconfont icon-right"></i>
      </a>
    
  </nav>

      </footer>
    

  </article>


          </div>
          
  <div class="comments" id="comments">
      <div id="disqus_thread">
        <noscript>
          Please enable JavaScript to view the
          <a href="//disqus.com/?ref_noscript">comments powered by Disqus.</a>
        </noscript>
      </div> 
    </div>
  </div>


        </div>
      </main>

      <footer id="footer" class="footer">

  <div class="social-links">
    
      
        
          <a href="https://github.com/bay1" class="iconfont icon-github" title="github"></a>
        
      
    
      
        
          <a href="http://weibo.com/3190704711/profile?topnav=1&wvr=6&is_all=1" class="iconfont icon-weibo" title="weibo"></a>
        
      
    
      
    
      
    
      
    
    
    
  </div>


<div class="copyright">
  <span class="copyright-year">
    
    &copy; 
     
      2016 - 
    
    2018
    <span class="author">bay1</span>
  </span>
</div>
      </footer>

      <div class="back-to-top" id="back-to-top">
        <i class="iconfont icon-up"></i>
      </div>
    </div>

    
  
  <script type="text/javascript">
    var disqus_config = function () {
        this.page.url = 'https://bay1.top/2018/06/21/微信公众号开发Django-网页授权/';
        this.page.identifier = '2018/06/21/微信公众号开发Django-网页授权/';
        this.page.title = '微信公众号开发Django-网页授权';
    };
    (function() {
    var d = document, s = d.createElement('script');

    s.src = '//https-blog-flywinky-top-1.disqus.com/embed.js';

    s.setAttribute('data-timestamp', +new Date());
    (d.head || d.body).appendChild(s);
    })();  
  </script>



    
  





  
    <script type="text/javascript" src="/lib/jquery/jquery-3.1.1.min.js"></script>
  

  
    <script type="text/javascript" src="/lib/slideout/slideout.js"></script>
  

  
    <script type="text/javascript" src="/lib/fancybox/jquery.fancybox.pack.js"></script>
  


    <script type="text/javascript" src="/js/src/even.js?v=2.6.0"></script>
<script type="text/javascript" src="/js/src/bootstrap.js?v=2.6.0"></script>
<script src="/js/prettify.js"></script>
<script type="text/javascript">
$(document).ready(function(){
 $('pre').addClass('prettyprint');
   prettyPrint();
 })
</script>
  </body>
</html>
